package com.cfpamf.ms.insur.pay.integration.wxpay;

import com.alibaba.fastjson.JSON;
import com.cfpamf.common.ms.exception.MSBizNormalException;
import com.cfpamf.common.ms.util.DateUtil;
import com.cfpamf.ms.insur.pay.config.WxPayProperties;
import com.cfpamf.ms.insur.pay.constant.BizExceptEnum;
import com.cfpamf.ms.insur.pay.dao.PayBankTrxBizWxMapper;
import com.cfpamf.ms.insur.pay.facade.constant.PayMethodEnum;
import com.cfpamf.ms.insur.pay.facade.constant.PayStatusEnum;
import com.cfpamf.ms.insur.pay.integration.BankPayService;
import com.cfpamf.ms.insur.pay.integration.dto.*;
import com.cfpamf.ms.insur.pay.pojo.po.PayBankTrxBizWxPO;
import com.cfpamf.ms.insur.wx.client.WechatPayClient;
import com.cfpamf.ms.insur.wx.enums.WxPayTradeStatus;
import com.cfpamf.ms.insur.wx.exception.WxPayException;
import com.cfpamf.ms.insur.wx.model.*;
import com.cfpamf.ms.insur.wx.request.*;
import com.cfpamf.ms.insur.wx.response.*;
import com.cfpamf.ms.insur.wx.util.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;

import static com.cfpamf.ms.insur.pay.pojo.convert.WxConvert.CONVERT;

/**
 * Create By zhengjing on 2019-09-24 17:24
 */
@Slf4j
@Service("WXPAY")
public class WxPayService implements BankPayService {
    private final static DateTimeFormatter DATE_TIME = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");


    @Autowired
    private WxClientManager clientManager;

    @Autowired
    PayBankTrxBizWxMapper payBankTrxBizWxMapper;

    @Override
    public BankPreCreateOrderRetDTO bankPreCreateOrder(BankPreCreateOrderParamDTO params) {
        try {
            String prePayId = getPrePayId(params);
            BankPreCreateOrderRetDTO bankRetDTO = new BankPreCreateOrderRetDTO();
            bankRetDTO.setBody(prePayId);
            return bankRetDTO;
        } catch (WxPayException e) {
            log.error("微信预下单失败", e);
            throw new MSBizNormalException(BizExceptEnum.WX_API_ERROR.getCode(), e.getMessage());
        }
    }

    @Override
    public BankPayOrderRetDTO bankPayOrder(BankPayOrderParamDTO params) {
        return null;
    }

    @Override
    public BankQueryOrderRetDTO bankQueryOrderInfo(BankQueryOrderParamDTO params) {
        WxPayQueryResponse payQueryResp = queryOrderTrade(params.getSourceSys(), params.getPayTrxNo());
        BankQueryOrderRetDTO bankRet = new BankQueryOrderRetDTO();
        bankRet.setPayTrxNo(payQueryResp.getOutTradeNo());
        if (payQueryResp.getTradeState() == WxPayTradeStatus.NOTPAY) {
            bankRet.setPayStatus(PayStatusEnum.TO_PAID.getStatusCode());
        } else if (payQueryResp.getTradeState() == WxPayTradeStatus.SUCCESS) {
            bankRet.setPayStatus(PayStatusEnum.PAY_SUCCESS.getStatusCode());
            bankRet.setBankPayTrxNo(payQueryResp.getTransactionId());
            bankRet.setOrderAmount(new BigDecimal(payQueryResp.getTotalFee()).divide(new BigDecimal(100.00), 2, BigDecimal.ROUND_HALF_DOWN));
            bankRet.setPayTime(DateUtil.parseDate(payQueryResp.getTimeEnd(), "yyyyMMddHHmmss"));
        } else if (payQueryResp.getTradeState() == WxPayTradeStatus.REFUND) {
            bankRet.setPayStatus(PayStatusEnum.REFUND_SUCCESS.getStatusCode());
            bankRet.setBankPayTrxNo(payQueryResp.getTransactionId());
            bankRet.setOrderAmount(new BigDecimal(payQueryResp.getTotalFee()).divide(new BigDecimal(100.00), 2, BigDecimal.ROUND_HALF_DOWN));
            bankRet.setPayTime(DateUtil.parseDate(payQueryResp.getTimeEnd(), "yyyyMMddHHmmss"));
        }
        return bankRet;
    }

    @Override
    public BankRefundOrderRetDTO bankRefundOrder(BankRefundOrderParamDTO params) {
        WxPayRefundResponse refundResp = tradeRefund(params);
        return new BankRefundOrderRetDTO();
    }

    @Override
    public BankQueryRefundRetDTO bankQueryRefund(BankQueryRefundParamDTO params) {
        WxPayRefundQueryResponse queryResp = refundQuery(params.getSourceSys(), params.getRefundTrxNo());
        BankQueryRefundRetDTO retDTO = new BankQueryRefundRetDTO();
        if (queryResp.getRefundRecords() != null && !queryResp.getRefundRecords().isEmpty()) {
            WxPayRefundQueryResponse.RefundRecord refundRecord = queryResp.getRefundRecords().get(0);
            if (refundRecord != null && Objects.equals(refundRecord.getRefundStatus(), "SUCCESS")) {
                retDTO.setPayTrxNo(queryResp.getOutTradeNo());
                retDTO.setBankPayTrxNo(queryResp.getTransactionId());
                retDTO.setRefundStatus(PayStatusEnum.REFUND_SUCCESS.getStatusCode());
                retDTO.setRefundAmount(new BigDecimal(refundRecord.getRefundFee()).divide(new BigDecimal("100.00"), 2, BigDecimal.ROUND_HALF_UP));
                retDTO.setOrderAmount(new BigDecimal(queryResp.getTotalFee()).divide(new BigDecimal("100.00"), 2, BigDecimal.ROUND_HALF_UP));
                retDTO.setRefundTime(DateUtil.parseDate(refundRecord.getRefundSuccessTime(), DateUtil.CN_LONG_FORMAT));
                retDTO.setBankRefundTrxNo(refundRecord.getRefundId());
                retDTO.setRefundTrxNo(refundRecord.getOutRefundNo());
            } else if (refundRecord != null && Objects.equals(refundRecord.getRefundStatus(), "CHANGE")) {
                retDTO.setRefundStatus(PayStatusEnum.REFUND_FAIL.getStatusCode());
            }
        } else {
            retDTO.setRefundStatus(PayStatusEnum.REFUNDING.getStatusCode());
        }
        return retDTO;
    }

    @Override
    public BankPayCheckNotifyRetDTO bankCheckPayNotify(String sourceSys, HttpServletRequest request) {
        BankPayCheckNotifyRetDTO retDTO = new BankPayCheckNotifyRetDTO();
        try {
            int contentLength = request.getContentLength();
            if (contentLength <= 0) {
                log.error("微信支付回调内容为空");
                retDTO.setPaySuccess(false);
                return retDTO;
            }
            String s = request.getAttribute("notifyXml").toString();
            retDTO.setRequestBody(JSON.toJSONString(s));
            getWechatPayClient(sourceSys).vaildParams(s);
            log.info("微信支付回调通知:{}", s);
            WxPayOrderNotifyResponse payNotifyResponse = getWechatPayClient(sourceSys).getMapper().readValue(s, WxPayOrderNotifyResponse.class);
            if (payNotifyResponse.isSuccess()) {
                retDTO.setPaySuccess(true);
                retDTO.setPayTrxNo(payNotifyResponse.getOutTradeNo());
                retDTO.setRespBody("<xml> \n" +
                        "  <return_code><![CDATA[SUCCESS]]></return_code>\n" +
                        "  <return_msg><![CDATA[OK]]></return_msg>\n" +
                        "</xml>");
                return retDTO;
            }
        } catch (Exception e) {
            log.error("微信支付回调处理失败", e);
        }
        retDTO.setPaySuccess(false);
        return retDTO;
    }

    @Override
    public BankRefundCheckNotifyRetDTO bankCheckRefundNotify(String sourceSys, HttpServletRequest request) {
        BankRefundCheckNotifyRetDTO retDTO = new BankRefundCheckNotifyRetDTO();
        WxPayRefundNotifyResponse refundNotifyResp = parseRefundNotify(sourceSys, request);
        if (refundNotifyResp.isSuccess() && refundNotifyResp.getReqInfo() != null
                && Objects.equals(refundNotifyResp.getReqInfo().getRefundStatus(), "SUCCESS")) {
            retDTO.setRefundSuccess(true);
            retDTO.setRefundTrxNo(refundNotifyResp.getReqInfo().getOutRefundNo());
            retDTO.setBankRefundTrxNo(refundNotifyResp.getReqInfo().getRefundId());
            retDTO.setRefundAmount(new BigDecimal(refundNotifyResp.getReqInfo().getRefundFee()).divide(new BigDecimal("100.00"), 2, BigDecimal.ROUND_HALF_DOWN));
            retDTO.setRefundCompleteTime(DateUtil.parseDate(refundNotifyResp.getReqInfo().getSuccessTime(), DateUtil.CN_LONG_FORMAT));
            retDTO.setRequestBody(request.getAttribute("notifyXml").toString());
            retDTO.setRespBody("<xml> \n" +
                    "  <return_code><![CDATA[SUCCESS]]></return_code>\n" +
                    "  <return_msg><![CDATA[OK]]></return_msg>\n" +
                    "</xml>");
            return retDTO;
        }
        return retDTO;
    }

    /**
     * 统一下单
     *
     * @param params
     * @return
     */
    public String getPrePayId(BankPreCreateOrderParamDTO params) {
        String tradeType = null;
        if (Objects.equals(PayMethodEnum.H5.getTypeCode(), params.getPayMethod())) {
            tradeType = "MWEB";
        } else if (Objects.equals(PayMethodEnum.QRCODE.getTypeCode(), params.getPayMethod())) {
            tradeType = "NATIVE";
        } else if (Objects.equals(PayMethodEnum.APP.getTypeCode(), params.getPayMethod())) {
            tradeType = "APP";
        } else if (Objects.equals(PayMethodEnum.TAS.getTypeCode(), params.getPayMethod())) {
            tradeType = "JSAPI";
        } else {
            throw new MSBizNormalException(BizExceptEnum.PAY_METHOD_ERROR);
        }
        WxPayProperties wxPayProperties = getWxPayProperties(params.getSourceSys());

        LocalDateTime orderLocalDateTime = LocalDateTime.ofInstant(params.getOrderTime().toInstant(), ZoneId.systemDefault());
        WxPayPrePayModel.WxPayPrePayModelBuilder prePayModelBuilder = WxPayPrePayModel.builder()
                .totalFee(params.getOrderAmount().multiply(new BigDecimal(100)).intValue())
                .timeStart(DATE_TIME.format(orderLocalDateTime))
                .timeExpire(DATE_TIME.format(orderLocalDateTime.plusMinutes(params.getOrderPeriod())))
                .outTradeNo(params.getPayTrxNo())
                .body(params.getProductName())
                .deviceInfo("WEB")
                .attach(params.getPayTrxNo())
                .spbillCreateIp(params.getOrderIp())
                .openId(params.getOpenId())
                .tradeType(tradeType)
                //wxPayProperties 可以
                .notifyUrl(wxPayProperties.getPayNotifyUrl());

        WxPayPrePayResponse resp = getWechatPayClient(params.getSourceSys()).execute(new WxPayPrePayRequest(prePayModelBuilder.build()));
        if (resp.isSuccess()) {
            return JSON.toJSONString(buildAppSignInfo(params.getPayMethod(), wxPayProperties.getMchKey(), resp));
        }
        throw new WxPayException("微信预下单失败:" + resp.errormsg());
    }

    /**
     * 微信支付状态查询
     *
     * @param orderNo
     * @return
     */
    public WxPayQueryResponse queryOrderTrade(String sourceSys, String orderNo) {
        WxPayQueryModel build = WxPayQueryModel.builder().outTradeNo(orderNo).build();
        WxPayQueryRequest queryRequest = new WxPayQueryRequest(build);
        WxPayQueryResponse resp = getWechatPayClient(sourceSys).execute(queryRequest);
        if (resp.isSuccess()) {
            return resp;
        }
        throw new WxPayException("微信订单查询失败" + orderNo + ":" + resp.errormsg());
    }

    /**
     * 微信退款
     *
     * @param params
     */
    public WxPayRefundResponse tradeRefund(BankRefundOrderParamDTO params) {
        WxPayRefundModel.WxPayRefundModelBuilder refundModelBuilder = WxPayRefundModel.builder()
                .transactionId(params.getBankPayTrxNo())
                .outRefundNo(params.getRefundTrxNo())
                .totalFee(params.getOrderAmount().multiply(new BigDecimal(100)).intValue())
                .refundFee(params.getRefundAmount().multiply(new BigDecimal(100)).intValue())
                .refundDesc(params.getRefundReason())
                .notifyUrl(getWxPayProperties(params.getSourceSys()).getRefundNotifyUrl());
        WxPayRefundRequest request = new WxPayRefundRequest(refundModelBuilder.build());
        WxPayRefundResponse resp = getWechatPayClient(params.getSourceSys()).execute(request);
        if (resp.isSuccess()) {
            return resp;
        }
        throw new WxPayException("微信退款失败" + params.getPayTrxNo() + ":" + resp.errormsg());
    }


    private WxPayRefundQueryResponse refundQuery(String sourceSys, String retrunTrxNo) {
        WxPayRefundQueryModel refundModelBuilder = WxPayRefundQueryModel.builder()
                .outRefundNo(retrunTrxNo).build();
        WxPayRefundQueryRequest request = new WxPayRefundQueryRequest(refundModelBuilder);
        WxPayRefundQueryResponse resp = getWechatPayClient(sourceSys).execute(request);
        if (resp.isSuccess()) {
            return resp;
        }
        throw new WxPayException("退款查询:" + resp.errormsg());
    }


    /**
     * 微信资金账单
     *
     * @param billDate
     * @return
     */
    public WxPayDownloadFundflowResponse getDownload(String sourceSys, LocalDate billDate) {
        WxDownloadFundflowModel model = WxDownloadFundflowModel.builder()
                .accountType("Basic")
                .billDate(DateTimeFormatter.ofPattern("yyyyMMdd").format(billDate))
                .tarType("GZIP").build();
        model.setSignType("HMAC-SHA256");
        WxPayDownloadFundflowRequest request = new WxPayDownloadFundflowRequest(model);
        return getWechatPayClient(sourceSys).execute(request);
    }

    /**
     * 微信对账单
     *
     * @param sourceSys
     * @param billDate
     * @return
     */
    public WxPayDownloadBillResponse getDownloadBill(String sourceSys, LocalDate billDate) {

        WxDownloadBillModel model = WxDownloadBillModel.builder()
                .billType("ALL")
                .billDate(DateTimeFormatter.ofPattern("yyyyMMdd").format(billDate))
                .tarType("GZIP").build();
//        model.setSignType("HMAC-SHA256");
        WxPayDownloadBillRequest request = new WxPayDownloadBillRequest(model,
                resp -> {
                    if (resp.isEnd()) {
                        //TODO 保存汇总数据
                        System.err.println(resp);
                    }
                    if (!CollectionUtils.isEmpty(resp.getDatas())) {
                        List<PayBankTrxBizWxPO> payBankTrxBizWxPOS = CONVERT.convertBizTrx(resp.getDatas());
                        payBankTrxBizWxPOS.forEach(trx -> {
                            trx.setSourceSys(sourceSys);
                            trx.setBillDate(billDate);
                        });
                        payBankTrxBizWxMapper.insertListInit(payBankTrxBizWxPOS);
                    }
                });
        return getWechatPayClient(sourceSys).execute(request);
    }

    /**
     * 关闭订单
     *
     * @param orderNo
     * @return
     */
    public WxCloseOrderResponse closeOrder(String sourceSys, String orderNo) {
        WxCloseOrderModel model = WxCloseOrderModel.builder()
                .outTradeNo(orderNo).build();
        WxCloseOrderRequest request = new WxCloseOrderRequest(model);
        return getWechatPayClient(sourceSys).execute(request);
    }

    /**
     * 解析微信退款通知
     *
     * @param sourceSys 业务系统
     * @param request   请求参数
     * @return
     */
    public WxPayRefundNotifyResponse parseRefundNotify(String sourceSys, HttpServletRequest request) {

        int contentLength = request.getContentLength();
        if (contentLength <= 0) {
            log.error("微信退款回调内容为空");
            return null;
        }
        try {
            WechatPayClient wechatPayClient = getWechatPayClient(sourceSys);
            String s = request.getAttribute("notifyXml").toString();
            log.info("微信退款参数{}:{}", sourceSys, s);
            WxPayRefundNotifyResponse resp = wechatPayClient.getMapper().readValue(s, WxPayRefundNotifyResponse.class);

            String reqStr = resp.decodeCiphertext(wechatPayClient.getApiKey());
            WxPayRefundNotifyResponse.ReqInfo reqInfo = wechatPayClient.getMapper().readValue(reqStr, WxPayRefundNotifyResponse.ReqInfo.class);
            resp.setReqInfo(reqInfo);
            return resp;
            // * （3）用key*对加密串B做AES-256-ECB解密（PKCS7Padding）
        } catch (Exception e) {
            throw new WxPayException("解析微信通知数据失败 " + sourceSys, e);
        }
    }

    /**
     * 构建预下单签名
     *
     * @param payMethod
     * @param partnerKey
     * @param resp
     * @return
     */
    private Map<String, String> buildAppSignInfo(String payMethod, String partnerKey, WxPayPrePayResponse resp) {
        SortedMap<String, String> appSignMap = new TreeMap<>();
        String str = UUID.randomUUID().toString().replaceAll("-", "");
        if (PayMethodEnum.APP.getTypeCode().equals(payMethod)) {// 为APP生成签名
            appSignMap.put("appid", resp.getAppId());
            appSignMap.put("partnerid", resp.getMchId());
            appSignMap.put("package", "Sign=WXPay");
            appSignMap.put("noncestr", str);
            appSignMap.put("timestamp", System.currentTimeMillis() / 1000 + "");
            // 除了app支付要单独传这个字段其他的都是在package
            appSignMap.put("prepayid", resp.getPrepayId());
            String appSign = WXPayUtil.generateSignature(appSignMap, partnerKey);
            appSignMap.put("paySign", appSign);

        } else {// 为APP之外的支付方式生成签名
            appSignMap.put("appId", resp.getAppId());
            appSignMap.put("package", "prepay_id=" + resp.getPrepayId());
            appSignMap.put("signType", "MD5");
            appSignMap.put("timeStamp", System.currentTimeMillis() / 1000 + "");
            appSignMap.put("nonceStr", str);
            //生成签名
            String appSign = WXPayUtil.generateSignature(appSignMap, partnerKey);
            appSignMap.put("sign", appSign);
        }

        if (PayMethodEnum.QRCODE.getTypeCode().equals(payMethod)) {
            appSignMap.put("code_url", resp.getCodeUrl());
        }

        if (PayMethodEnum.H5.getTypeCode().equals(payMethod)) {
            appSignMap.put("mwebUrl", resp.getMwebUrl());
        }
        return appSignMap;
    }

    /**
     * TODO config
     *
     * @return
     */
    private WechatPayClient getWechatPayClient() {
        return clientManager.getPrimaryClient();
    }

    private WechatPayClient getWechatPayClient(String systemCode) {
        return clientManager.getClientBySystemCode(systemCode);
    }

    private WxPayProperties getWxPayProperties() {
        return clientManager.getPrimaryProperties();
    }

    /**
     * 根据系统编码获取 配置熟悉
     *
     * @param systemCode
     * @return
     */
    private WxPayProperties getWxPayProperties(String systemCode) {
        return clientManager.getPropertiesBySystemCode(systemCode);
    }
}
